<?php
/**
 * Main Database access
 *
 * @author Patrice Ferlet <metal3d@copix.org> 
 * @package db
 * @license http://opensource.org/licenses/lgpl-3.0.html GNU Lesser General Public License Version 3.0 (LGPLv3)
*/

/**
* iPDB interface that PDB connections must use
* Iterator and ArrayAccess are needed !
*/

interface iPDB extends Iterator, ArrayAccess{
    /**
    * Connect to database
    * @param PStandardObject $options
    */
    public function connect(PStandardObject $options);

    /**
    * Execute query on database
    * @param string $query
    */
    public function doQuery($query);
    
    /**
    * Return fields on a table
    * @param string $tablename
    * @return array fields
    */
    public function getFields($tablename);

    /**
    * Get indexes fields with options
    * @param string $tablename
    * @return array indexes
    */
    public function getIndexes($tablename);

    /**
    * Insert a record into database
    * @param PDBRecord $record
    * @return bool $noerror
    */
    public function insert(PDBRecord $record);

    /**
    * Delete a record from database
    * @param PDBRecord $record
    * @return bool $noerror
    */
    public function delete(PDBRecord $record);

    /**
    * Update a record from database
    * @param PDBRecord $record
    * @return bool $noerror
    */
    public function update(PDBRecord $record);
}


/* 
 * PDB Class Proxy
 * It loads drivers on demand
 *
 * @author Patrice Ferlet <metal3d@copix.org> 
 */
class PDB{

    static protected $instance=array();
    private $connectionname = "default";
    private $indexes;

    /**
    * Set Database connection name
    * @param string $name
    */
    function setName($name){
        $this->connectionname = $name;
    }

    /**
    * Get connection name
    * @return string $name
    */
    function getName(){
        return $this->connectionname;
    }

    /**
    * Get a single connection from database profiles
    * @param string $name
    * @return PDB $connection
    */
    function instance ($name="default"){
        if(isset(PDB::$instance[$name])) return PDB::$instance[$name];
        $drv = _kernel()->getConfiguration()->get('root/project/databases/'.$name)->name('driver@param');
        
        //include driver class then instanciate a new object
        require_once "drivers/".$drv.".db.class.php";
        $class = 'P'.ucfirst($drv).'DB';
        $db = new $class;

        //connect
        $db->connect(_obj(array(
            'username' => _kernel()
                        ->getConfiguration()
                        ->get('root/project/databases/'.$name)
                        ->name('username@param'),
            'password' => _kernel()
                        ->getConfiguration()
                        ->get('root/project/databases/'.$name)
                        ->name('password@param'),
            'server'     => _kernel()
                        ->getConfiguration()
                        ->get('root/project/databases/'.$name)
                        ->name('host@param'),
            'database' => _kernel()
                        ->getConfiguration()
                        ->get('root/project/databases/'.$name)
                        ->name('db@param'),
            
        )));
        $db->connectionname=$name;
        PDB::$instance[$name] = $db;
        return PDB::$instance[$name];

    }

}


/**
* PDAO - Data Access
* Implements a simple way to get datas
*/
class PDAO {
    
    private $db = null;
    private $tab = null;

    /**
    * Constructor to get DAO for a table
    *
    * @param string $tablename
    * @param string $connectionname
    */
    public function __construct($table,$db="default"){
        $this->db = _db($db);
        $this->tab = $table;
    }

    /**
    * Get results by indexes
    *
    * @param string index1
    * @param string index2
    * @param string ...
    * @return PResult
    */

    public function get(){
        $args = func_get_args();
        $indexes = $this->db->getIndexes($this->tab);
        if(count($args) != count($indexes)){
            $first = true;
            $mess = 'Error, PDAO::get needs '.count($indexes).' arguments to get this indexes: ';
            foreach ($indexes as $i){
                if (!$first) $mess.=', ';
                $mess .= "$i";
                $first = false;
            }
            throw new PException($mess);
        }


        $sql = 'select * from '.$this->tab.' where ';
        $first=true;
        $loop=0;
        foreach ($indexes as $i){
            if(!$first) $sql .= ' AND ';
            $first=false;
            $sql .= "$i="."'$args[$loop]'";
        }

        $res = $this->db->doQuery($sql);
        $res->setRecord($this->tab);
        return $res;
    }
    

}

/**
* PDBResult allows to access datas like an array 
*
*/

class PDBResult implements Iterator, ArrayAccess{

    private $db;
    private $table = FALSE;
    private $dbrecord = FALSE;

    /**
    * Construct a PDBResult from database connection
    * @param PDB $connection
    */
    public function __construct(&$db){
       $this->db = $db; 
    }

    /**
    * Set PDBRecord from table
    * @param string $tablename
    */
    public function setRecord($table){
        $this->table = $table;
        $this->dbrecord = PDBRecord::instance($table,$this->db);
    }

    /**
    * Check if current (this) object is made for PDBRecord
    * @return bool $isRecord
    */
    public function isRecord(){
        return (bool)($this->dbrecord instanceof PDBRecord);
    }

    /* Iterator */

    /**
    * Return current record, if isRecord(), return PDBRecord, either returns PDBResult
    * @return PDBRecord,PDBResult $record
    */
    public function current(){
        if ($this->isRecord()){
            //cast current result to PDBRecord
            return $this->dbrecord->set($this->db->current());
        }
        return $this->db->current();
    }


    /**
    * Go to next record
    * @return PDBResult $record
    */
    public function next(){
        return $this->db->next();
    }

    /**
    * Check data pointer position
    * @return int $key
    */
    public function key(){
        return $this->db->key();
    }

    /**
    * Return to first data
    */
    public function rewind(){
        $this->db->rewind();
    }

    /**
    * Check position validity
    * @return bool
    */
    public function valid(){
        return $this->db->valid();
    }

   /* ArrayAccess */

    /**
    * Check from if record exists at given position
    * @param int $position
    * @return bool $exists
    */
    public function offsetExists($offset){
        return $this->db->offsetExists($offset);
    }

    /**
    * Get record at given position on array
    * @param int $position
    * @return PDBRecord/PDBResult $record
    */
    public function offsetGet($offset){
        if ($this->isRecord()){
            //cast current result to PDBRecord
            return $this->dbrecord->set($this->db->offsetGet($offset));
        }
        return $this->db->offsetGet($offset);
    }

    /**
    * Must give an error, exists fro ArrayAccess SPL implementation
    */
    public function offsetUnset($offset){
        return $this->db->offsetUnset($offset);
    }

    /**
    * Must give an error, exists fro ArrayAccess SPL implementation
    */
    public function offsetSet($offset, $val){
        return $this->db->offsetSet($offset, $val);
    }

}


/**
* PDBRecord is a record that can be read, updated or inserted into database
*/

class PDBRecord extends PStandardObject {

    private $db = null;
    private $table = "";
    private $fields = array();
    private $indexes = array();
    static  $_instance = array();
    

    /**
    * Map PDBRecord to a data table
    * @param string $tablename
    * @param string $connection_name
    */
    public function map($tablename, $connectionname="default"){
        if ($connectionname instanceof PDB) $this->db = $connectionname;
        else  $this->db = _db($connectionname);
     
        $this->table = $tablename; 
        $this->initializeFields();
        $this->indexes = $this->db->getIndexes($tablename);
    }

    /**
    * Instance of PDBRecord by table and connection. It returns a CLONE !
    * @param string $tablename
    * @param mixed $connection (name or db instance)
    * @return PDBRecord clone
    */
    public function instance ($tablename, $connectionname="default"){
        $connectionname = ($connectionname instanceof PDB) ? $connectionname->getName() : $connectionname;
        if (!isset(PDBRecord::$_instance[$connectionname][$tablename])) {
            PDBRecord::$_instance[$connectionname][$tablename] = new PDBRecord();
            PDBRecord::$_instance[$connectionname][$tablename]->map ($tablename, $connectionname);
        }
        return clone PDBRecord::$_instance[$connectionname][$tablename];
    }

    /**
    * Initialize table fields on record
    */
    public function initializeFields() {
        $this->fields = $this->db->getFields($this->table);
        foreach($this->fields as $field){
            $this->vars[$field->name] = null;
        }
    }


    /**
    * Get Tablename used on record
    * @return string $tablename
    */
    public function getTable(){
        return $this->table;
    }

    /**
    *
    */
    public function getFields(){
        return $this->fields;
    }

    /**
    * Return connected database to this record
    * @return PDB $connection
    */
    public function getDB(){
        return $this->db;
    }

    /**
    * Map table fields to properties
    * @param mixed $dataset
    */
    public function set($data){
        if(is_array($data)){
            foreach ($data as $key=>$val){
                $this->vars[$key] = $val;
            }
        }
        elseif(is_object($data)){
            $prop = get_object_vars($data);
            foreach ($prop as $key=>$val){
                $this->vars[$key] = $val;
            }
        }
        return $this;
    }

    /* Datas manipulation */
    public function insert(){
        $this->db->insert($this);
    }
    public function update(){}
    public function delete(){}

}
